﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ReadAndWrite
{
    /// <summary>
    /// Cette classe permet de lire un fichier CSV en fonction d'un séparateur 
    /// mais aussi d'écrire un fichier CSV selon un format défini.
    /// </summary>
    public class ReadAndWriteCSV : ReadAndWrite
    {
        #region Propriété(s)

        /// <summary>
        /// Séparateur utilisé pour séparer les valeurs entre elles dans le fichier (Accessible en lecture et écriture).
        /// </summary>
        public String Separateur
        {
            get;
            set;
        }

        #endregion

        #region Constructeur(s)

        /// <summary>
        /// Constructeur sans paramètre
        /// </summary>
        public ReadAndWriteCSV():base()
        {
            Separateur = String.Empty;
        }

        /// <summary>
        /// Constructeur avec paramètre
        /// </summary>
        /// <param name="path">Le chemin du fichier</param>
        /// <param name="separateur">Le séparateur de ce fichier</param>
        public ReadAndWriteCSV(String path, String separateur):base(path)
        {
            Separateur = separateur;
        }

        #endregion

        #region Méthode(s)

        /// <summary>
        /// Permet d'écrire dans un fichier CSV l'ensemble des objet passé en paramètre.
        /// </summary>
        /// <param name="header">Header du fichier</param>
        /// <param name="listObject">Liste des objets à capitaliser dans ce fichier CSV</param>
        /// <returns>Retourne un booléan permettant de savoir si l'opération s'est bien passée.</returns>
        public bool WriteCSV(String header, List<ISerializableCSV> listObject, Boolean overwrite = false, Boolean protect = false)
        {
            StringBuilder builder = new StringBuilder();

            if(!overwrite)
                File.Delete(Path);
            
            if (!File.Exists(Path))
                builder.AppendLine(header);


            if (listObject != null)
            {
                foreach (ISerializableCSV icsv in listObject)
                {
                    StringBuilder builderTmp = new StringBuilder();
                    List<Object> list = icsv.exposeAttributes();

                    for (int i = 0; i < list.Count; ++i)
                    {
                        if (protect)
                            builderTmp.Append((list[i] != null ? ConvertStringToStringCSV(Convert.ToString(list[i])) : String.Empty) + Separateur);
                        else
                            builderTmp.Append((list[i] != null ? Convert.ToString(list[i]) : String.Empty) + Separateur);
                    }

                    builder.AppendLine(builderTmp.ToString());
                    builderTmp.Clear();
                }
            }

            return Write(builder.ToString());
        }

        /// <summary>
        /// Permet d'écrire dans un fichier CSV l'ensemble des objet passé en paramètre.
        /// On utilise le header définit par ISerializableCSV.
        /// </summary>>
        /// <param name="listObject">Liste des objets à capitaliser dans ce fichier CSV</param>
        /// <returns>Retourne un booléan permettant de savoir si l'opération s'est bien passée.</returns>
        public bool WriteCSV(List<ISerializableCSV> listObject, Boolean overwrite = false, Boolean protect = false)
        {
            StringBuilder builder = new StringBuilder();
            if (listObject.Count > 0)
            {
                if (!overwrite)
                    File.Delete(Path);

                if (!File.Exists(Path))
                    builder.AppendLine(listObject[0].exposeHeader(Separateur));

                if (listObject != null)
                {
                    foreach (ISerializableCSV icsv in listObject)
                    {
                        StringBuilder builderTmp = new StringBuilder();
                        List<Object> list = icsv.exposeAttributes();

                        for (int i = 0; i < list.Count; ++i)
                        {
                            if (protect)
                                builderTmp.Append((list[i] != null ? ConvertStringToStringCSV(Convert.ToString(list[i])) : String.Empty) + Separateur);
                            else
                                builderTmp.Append((list[i] != null ? Convert.ToString(list[i]) : String.Empty) + Separateur);
                        }
  
                        builder.AppendLine(builderTmp.ToString());
                        builderTmp.Clear();
                    }
                }

                return Write(builder.ToString());
            }
            return false;
        }

        /// <summary>
        /// Fonction statique permettant de convertir un objet en chaine de caractères.
        /// Remplace toutes les " par des "", Insére au début et à la fin de la chaine des ".
        /// Fait appel à la méthode ToString de l'objet, cette méthode doit donc être défini pour les objets complexes.
        /// </summary>
        /// <param name="obj">Objet à convertir</param>
        /// <returns>Renvoit la chaine de caractère formattée</returns>
        private String ConvertStringToStringCSV(object obj)
        {
            String res = String.Empty;
            const string quote = "\"";

            if (obj != null)
            {
                res = obj.ToString();
                // Remplace les potentiels double quote par deux doubles-quote.
                res = res.Replace(quote, quote + quote);
                // Insére au début de la chaine une double quotte et en fin de chaine une double quote aussi.
                res = res.Insert(0, quote);
                res = res.Insert(res.Length, quote);
            }
            else
                res = quote + quote;

            return res;
        }

        /// <summary>
        /// Permet de lire un fichier CSV et de retourner l'ensemble des objets en fonction du type passé en paramètre
        /// </summary>
        /// <param name="type">Type d'objet à retourner, c'est à dire le type d'une classe. Cette classe doit posséder un constructeur vide.</param>
        /// <returns>Liste des objets lus dans le fichier. Vide si le fichier n'a pas été trouvé ou si il est vide.</returns>
        public List<ISerializableCSV> ReadCSV(Type type)
        {
            List<ISerializableCSV> listObject = new List<ISerializableCSV>();

            string[] lines = ReadLines();

            if (lines != null)
            {
                // On ne prend pas en compte la ligne de header
                for (int i = 1; i < lines.Length; ++i)
                {
                    ISerializableCSV obj = ((ISerializableCSV)(Activator.CreateInstance(type)));
                    string[] data = lines[i].Split(new string[] { Separateur }, StringSplitOptions.None);
                    obj.importAttributes(data);
                    listObject.Add(obj);
                }
            }

            return listObject;
        }

        #endregion
    }
}
